package util4;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author YanZhen_Li
 * @version 1.0
 * @title BatchUpdateUtils
 * @description 执行sql的工具类，我发现我们执行sql的时候为了防止数据库卡死和传输sql长度的影响，所以我打算写一个工具类，
 * 用来批量执行sql
 * @create 2024/4/28 9:51
 */
public class BatchSQLExecutionUtils {

    /**
     * 用来批量执行sql的方法，同时可以对每批次执行的结果进行处理
     *
     * @param innerList          传入需要批量执行的sql
     * @param processOfExecution 执行过程
     * @param r                  返回结果处理
     * @param count              每批执行的数量
     *                           主方法的泛型参数说明：
     *                           - `L`: 表示内部列表（innerList）中元素的类型，即待执行SQL或相关数据的类型。
     *                           - `K`: 表示处理过程（processOfExecution）返回的结果类型，这取决于SQL执行后如何被转换或包装，
     *                           - `P`: 是一个BiFunction泛型接口，接受一个`List<L>`，返回类型为`K`。它定义了如何处理每批次SQL的逻辑。
     *                           你可以写一些自己对对象的处理逻辑并调用mapper中的方法，然后返回一个`K`类型的结果。这个K的类型是
     *                           需要处理的结果数据，当然可以是任何类型，如List、Map等。不过推荐您返回一个int或Integer类型来表
     *                           示执行成功的条数，让后进行计算。
     *                           - `R`: 是另一个BiFunction泛型接口，接受前面步骤产生的`K`类型结果及累积结果`RK`，返回更新后的累积结果`RK`。
     *                           用于累计或变换每批次执行的结果。
     *                           - `RK`: 表示最终返回结果的类型，用户可通过`R`函数进一步处理`K`类型的结果。
     * @return
     */
    public static <
            L,
            K,
            RK,
            P extends Function<List<L>, K>,
            R extends BiFunction<K, RK, RK>
            >
    RK bachSqlExecution(List<L> innerList, P processOfExecution, R r, int count) {
        return bachSqlExecution(innerList, (List<L> k, RK rk) -> {
            K resoutValue = processOfExecution.apply(k);
            return r.apply(resoutValue, rk);
        }, count);
    }

    /**
     * 用来批量执行sql的方法，同时可以把每次执行的结果累加起来(您可以直接返回受影响条数，那样就会返回执行成功条数)
     *
     * @param innerList          传入需要批量执行的sql
     * @param processOfExecution 执行过程
     * @param count              每批执行的数量
     *                           主方法的泛型参数说明：
     *                           - `<L>`: 表示内部列表（innerList）中元素的类型，即待执行SQL或相关数据的类型。
     *                           - `<P>`: 是一个BiFunction泛型接口，接受一个`List<L>`，返回类型为`K`。它定义了如何处理每批次SQL的逻辑。
     *                           你可以写一些自己对对象的处理逻辑并调用mapper中的方法，然后返回一个`K`类型的结果。这个K的类型是
     *                           需要处理的结果数据，当然可以是任何类型，如List、Map等。不过推荐您返回一个int或Integer类型来表
     *                           示执行成功的条数，让后进行计算。
     * @return
     */
    public static <
            L,
            F extends Function<List<L>, Integer>
            >
    Integer bachSqlExecutionReturnTotal(List<L> innerList, F processOfExecution, int count) {
        return bachSqlExecution
                (
                        innerList,
                        processOfExecution,
                        (Integer k, Integer rk) -> {
                            if (k != null) {
                                if (rk == null) {
                                    return k;
                                } else {
                                    return k + rk;
                                }
                            } else {
                                return null;
                            }
                        },
                        count
                );
    }

    /**
     * 用来批量执行sql的方法，同时可以对每批次执行的结果进行处理
     *
     * @param innerList          传入需要批量执行的sql
     * @param processOfExecution 执行过程
     * @param count              每批执行的数量
     *                           主方法的泛型参数说明：
     *                           - `L`: 表示内部列表（innerList）中元素的类型，即待执行SQL或相关数据的类型。
     *                           - `K`: 表示处理过程（processOfExecution）返回的结果类型，这取决于SQL执行后如何被转换或包装，
     *                           - `P`: 是一个BiFunction泛型接口，接受一个`List<L>`分批之后的数据和第二个参数为上次执行结果RK，返回类型为`RK`。它定义了如何处理每批次SQL的逻辑。
     *                           你可以写一些自己对对象的处理逻辑并调用mapper中的方法，让后自己处理数据并放回。
     *                           - `RK`: 表示最终返回结果的类型，用户可通过`R`函数进一步处理`K`类型的结果。
     * @return
     */
    public static <
            L,
            RK,
            P extends BiFunction<List<L>, RK, RK>
            >
    RK bachSqlExecution(List<L> innerList, P processOfExecution, int count) {
        if (innerList == null || innerList.isEmpty()) {
            return null;
        }
        RK rk = null;
        int total = innerList.size();
        if (total <= count) {
            rk = processOfExecution.apply(innerList, rk);
        } else {
            int i = 0;
            int j = 0;
            while (i < total) {
                j = i + count;
                if (j > total) {
                    j = total;
                }
                List<L> subList = innerList.subList(i, j);
                rk = processOfExecution.apply(subList, rk);
                i = j;
            }
        }
        return rk;
    }

    /**
     * 批量执行但不返回结果
     * @param innerList 入参处理列表
     * @param processOfExecution 执行过程
     * @param count 每批次执行条数
     * @return
     * @param <L> 执行的输入类型
     * @param <P> 执行过程，包括sql语句
     */
    public static <L,P extends Consumer<List<L>>>
    void bachSqlExecutionNoReturn(List<L> innerList, P processOfExecution, int count) {
        bachSqlExecution(innerList, (a,b) -> {
            processOfExecution.accept(a);
            return b;
        }, count);
    }

    // 测试代码/使用案例
    public static void main(String[] args) {
        // 测试内部类
        @Data
        @AllArgsConstructor
        @NoArgsConstructor
        class User {
            private String Id;
            private String name;
            private Integer age;
            private Character sex;
            private Date updateTime;

            public User(String name, Integer age, Character sex) {
                this.name = name;
                this.age = age;
                this.sex = sex;
            }
        }
        class UserMapper {
            /**
             * 模拟批量新增
             * @param userList
             * @return
             */
            public Integer insert(List<User> userList) {
                return userList.size();
            }

            /**
             * 模拟批量新增返回主键
             * @param userList
             * @return
             */
            public List<User> insertReturnId(List<User> userList) {
                return userList.stream().map(u -> {
                    u.setId(UUID.randomUUID().toString());
                    return u;
                }).collect(Collectors.toList());
            }

            /**
             * 模拟批量更新并设置更新时间
             * @param userList
             * @param date
             * @return
             */
            public int update(List<User> userList, Date date) {
                userList.forEach(u -> u.setUpdateTime(date));
                return userList.size();
            }
        }
        // 测试代码/案例代码
        // 模拟执行sql的mapper
        UserMapper userMapper = new UserMapper();
        List<User> userList = new ArrayList<>();
        // 使用for循环制作1000条模拟数据
        for (int i = 0; i < 1000; i++) {
            userList.add(new User("name" + i, i, i % 2 == 0 ? '男' : '女'));
        }
        // ? 1、模拟分批执行返回受影响条数
        System.out.println("开始执行案例1：");
        Integer total = bachSqlExecutionReturnTotal(userList, (List<User> u) -> userMapper.insert(u), 100);
        System.out.println("首影响条数为：" + total);
        // ? 2、模拟分批执行新增返回主键
        System.out.println("开始执行案例2：");
        List<User> resultUserList = bachSqlExecution(
                userList,
                userMapper::insertReturnId,
                (k, rk) -> {
                    if (rk == null) {
                        return (List<User>) k;
                    } else {
                        rk.addAll((List<User>) k);
                        return rk;
                    }
                }, 100);
        System.out.println("新增返回主键结果集合1：" + resultUserList);
        // ? 3、模拟分批执行新增返回主键
        System.out.println("开始执行案例3：");
        List<User> resultUserList2 = bachSqlExecution(userList, (list, rk) -> {
            List<User> users = userMapper.insertReturnId(list);
            if (rk == null) {
                rk = new ArrayList<>();
                rk.addAll(users);
            } else {
                rk.addAll(users);
            }
            return rk;
        }, 100);
        System.out.println("新增返回主键结果集合2：" + resultUserList2);
        // ? 4、模拟分批执行但不反悔任何数据
        System.out.println("开始执行案例4：");
        bachSqlExecutionNoReturn(userList, (l) -> {
            System.out.println("正在执行 -> " + l);
            userMapper.insert(l);
        }, 100);
        // ? 5、模拟根据主键更新并统一设置更新时间
        System.out.println("开始执行案例5：");
        Date updateTime = new Date();
        bachSqlExecutionNoReturn(userList, (l) -> {
            userMapper.update(l, updateTime);
            System.out.println("正在执行 -> " + l);
        }, 100);
    }
}
